package com.golxen.framework.common.server.agent;


import com.golxen.framework.GameProtocol;
import com.golxen.framework.PBPacket.PBPacket;
import com.golxen.framework.common.BuiltinPlugin;
import com.golxen.framework.netty.session.agent.ServerClientMgr;
import com.golxen.framework.redis.RedisServerOnlineCount;
import com.golxen.utils.LogUtil;
import com.mysql.cj.util.LogUtils;
import io.netty.channel.Channel;
import org.slf4j.Logger;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 代理服玩家管理类，和玩家客户端连接
 */
public class AgentPlayerMgr {

	private static Map<Long, Long> loginMap = new ConcurrentHashMap<>();

	private static Logger logger = LogUtil.COMMON;

	private static Map<Long, AgentPlayer> onlineMap; // 在线玩家
	private static Map<Long, Channel> tempChannelMap; //在游戏服判断是否可以登录时暂时存放

	private static List<Long> listenIds; // 记录数据包的玩家id列表
	private static boolean isRecord = false; // 是否记录数据包开关
	private static boolean isRecordDetail = false; // 是否记录完整数据具体内容

	private static Timer timer; // 延迟执行线程

	public static boolean init() {
		onlineMap = new ConcurrentHashMap<>();
		tempChannelMap = new ConcurrentHashMap<>();
		listenIds = new ArrayList<>();

		timer = new Timer("delay-thread");

		RedisServerOnlineCount.setOnlineCount(BuiltinPlugin.getServerId(), 0);
		return true;
	}

	public static AgentPlayer getOnlineUser(long userId) {
		return onlineMap.get(userId);
	}

	public static Map<Long, AgentPlayer> getOnlineMap() {
		return onlineMap;
	}

	public static void addToOnlineMap(long userId, Channel channel) {
		synchronized (onlineMap) {
			AgentPlayer player = onlineMap.get(userId);
			if (player == null) {
				player = new AgentPlayer(userId, channel);
				onlineMap.put(userId, player);

				RedisServerOnlineCount.setOnlineCount(BuiltinPlugin.getServerId(), onlineMap.size());
			} else {
				player.setChannel(channel);
				RedisServerOnlineCount.setOnlineCount(BuiltinPlugin.getServerId(), onlineMap.size());
			}
		}
	}

	public static void addOnline(long userId, Channel channel) {
		addToOnlineMap(userId, channel);
		PBPacket message = new PBPacket(GameProtocol.S_PLAYER_LOGIN,userId);
		ServerClientMgr.routeGame(message);
	}

	public static void removeOnlineDelay(long userId, Channel channel) {
		timer.schedule(new DelayOffline(userId, channel), 500);
	}

	static class DelayOffline extends TimerTask {
		private long userId;
		private Channel channel;

		private DelayOffline(long userId, Channel channel) {
			this.userId = userId;
			this.channel = channel;
		}

		@Override
		public void run() {
			AgentPlayerMgr.removeOnline(userId, channel, false);
		}

	}

	//游戏服判断3分钟心跳发
	public static boolean kickOutPlayer(long userId) {
		AgentPlayer player = getOnlineUser(userId);
		LogUtil.COMMON.debug(" kick out player userId={} AgentPlayer {}", userId, player == null ? "is null" : "");
		if (player != null) {
			removeOnline(userId, player.getChannel(), true);
			return true;
		} else {
			removeOnline(userId, null, true);
		}

		return false;
	}

	public static void removeOnline(long userId, Channel channel, boolean isKick) {
		try {
			logger.debug(" idle time out cid={} userId={}", channel == null ? "" : channel.id().asShortText(), userId);
			offline(userId, channel, isKick);
			Channel tempChannel = AgentPlayerMgr.removeTempChannel(userId);
			if (tempChannel != null) {
				logger.debug(" idle time out close temp channel cid={} tcid={} userId={}",
						channel == null ? "" : channel.id().asShortText(), tempChannel.id().asShortText(), userId);
				tempChannel.close().sync();
			}
		} catch (InterruptedException e) {
			logger.error("idle time out  removeOnline error cid={} userId={}",
					channel == null ? "" : channel.id().asShortText(), userId, e);
		}
	}

	private static void offline(long userId, Channel channel, boolean isKick) {
		boolean isRemoveOnline = true;
		AgentPlayer player = getOnlineUser(userId);

		if (player == null || channel != player.getChannel()) {
			logger.debug("客户端 当前用户已经从在线列表中清除了, userId : " + userId);
			isRemoveOnline = false;
		}

		logger.debug(" idle time out call offline cid={} userId={}, isRemoveOnline={}",
				channel == null ? "" : channel.id().asShortText(), userId, isRemoveOnline);

		if (isRemoveOnline) {
			onlineMap.remove(userId);
			RedisServerOnlineCount.setOnlineCount(BuiltinPlugin.getServerId(), onlineMap.size());

			player.closeChannel();
		}

		logger.debug(" idle timeout send smsg cid={} userId={} isKick={}",
				channel == null ? "" : channel.id().asShortText(), userId, isKick);

		gameServerOffline(userId, isKick);
	}

	private static void gameServerOffline(long userId, boolean isKick) {
		int protocol = GameProtocol.S_PLAYER_LOGOUT;
		if (isKick) {
			protocol = GameProtocol.S_KICK_OUT_PLAYER;
		}
		PBPacket castleReq = new PBPacket(protocol);
		castleReq.setPlayerId(userId);
		ServerClientMgr.routeGame(castleReq);
	}

	/**
	 * <pre>
	 * 判断玩家id是否在协议记录列表中
	 * </pre>
	 *
	 * @param userId
	 * @return
	 */
	public static boolean isRecord(long userId) {
		try {
			return isRecord && listenIds.contains(userId);
		} catch (Exception e) {
			logger.error(Thread.currentThread().getStackTrace()[1].getMethodName() + ":", e);
			return false;
		}
	}

	/**
	 * 是否开启协议数据包详细内容
	 */
	public static boolean isRecordDetail() {
		return isRecordDetail;
	}

	/**
	 * <pre>
	 * 开始监听指定玩家的数据包
	 * </pre>
	 *
	 * @param isDetail 是否记录数据包的完整信息
	 * @param userIds  被监听数据包的玩家列表
	 */
	public static void startListen(boolean isDetail, long[] userIds) {
		synchronized (listenIds) {
			listenIds.clear();
			for (long userId : userIds) {
				listenIds.add(userId);
			}
		}
		isRecord = true;
		isRecordDetail = isDetail;
	}

	/**
	 * 停止监听指定玩家的数据包
	 */
	public static void stopListen() {
		synchronized (listenIds) {
			listenIds.clear();
		}
		isRecord = false;
		isRecordDetail = false;
	}

	public static void stop() {
		try {
			for (Channel channel : tempChannelMap.values()) {
				channel.close().sync();
			}

			for (Map.Entry<Long, AgentPlayer> entry : onlineMap.entrySet()) {
				AgentPlayer temp = entry.getValue();
				temp.closeChannel();
			}
		} catch (Exception e) {
			logger.error(Thread.currentThread().getStackTrace()[1].getMethodName() + ":", e);
			logger.error("Client set close client session exception.");
		}
	}

	public static void addTempChannel(long userId, Channel channel) {
		tempChannelMap.put(userId, channel);
	}

	public static Channel removeTempChannel(long userId) {
		return tempChannelMap.remove(userId);
	}

	/**
	 * 判断是否可登录
	 * 
	 * @param userId
	 * @return
	 */
	public synchronized static boolean canLogin(long userId) {
		long nowTime = System.currentTimeMillis();
		long lastLoginTime = 0;
		if (loginMap.containsKey(userId)) {
			lastLoginTime = loginMap.get(userId);
		}

		if (nowTime - lastLoginTime > 1000) {
			loginMap.put(userId, nowTime);
			return true;
		}
		return false;
	}
}
